package xyz.scootaloo.alg.tool;

import java.util.Scanner;

/**
 * 五子棋
 * @author flutterdash@qq.com
 * @since 2021/5/27 7:49
 */
public class GomokuGame {

    public static void main(String[] args) {
        int 边长 = 8;
        五子棋类 游戏 = new 五子棋类(边长);
        玩游戏(游戏);
    }

    private static void 玩游戏(五子棋类 五子棋) {
        五子棋.欢迎();
        do {
            五子棋.绘制棋盘();
            五子棋.下棋();
        } while (!五子棋.产生胜利者());
        五子棋.游戏结束();
        五子棋.绘制棋盘();
    }

    private static class 五子棋类 {
        private final char[][] 棋盘;
        private boolean 是否轮到玩家回合 = true;
        private final Scanner 控制台扫描器 = new Scanner(System.in);
        private final 裁判类 裁判;

        // 根据棋盘的边长生成棋盘, 例如 new Gomoku(15) 则代表创建边长为15大小的棋盘
        public 五子棋类(int 棋盘边长) {
            if (棋盘边长 <= 0)
                throw new IllegalArgumentException("棋盘大小至少是0");
            棋盘 = new char[棋盘边长][棋盘边长];

            裁判 = new 裁判类(棋盘, 5);
            初始化棋盘();
        }

        // 执行初始化, 将棋盘初始化成未下子的状态
        private void 初始化棋盘() {
            int 棋盘边长 = 棋盘.length;
            for (int 行坐标 = 0; 行坐标<棋盘边长; 行坐标++) {
                for (int 列坐标 = 0; 列坐标<棋盘边长; 列坐标++) {
                    棋盘[行坐标][列坐标] = 棋子类型.空位;
                }
            }
        }

        public void 欢迎() {
            println("五子棋游戏");
        }

        // 绘制棋盘, 将 board 数组的内容显示在屏幕上
        public void 绘制棋盘() {
            int 棋盘边长 = 棋盘.length;
            // 输出一行下标
            for (int i = 0; i<=棋盘边长; i++)
                print(对齐数字(i));
            println();

            // 输出余下的下标
            for (int i = 1; i<=棋盘边长; i++) {
                print(对齐数字(i));
                for (int column = 0; column<棋盘边长; column++) {
                    print(棋盘[i - 1][column] + "  ");
                }
                println();
            }
        }

        private String 对齐数字(int number) {
            if (number < 10)
                return number + "  ";
            return number + " ";
        }

        public void 下棋() {
            坐标类 坐标;
            if (是否轮到玩家回合) {
                坐标 = 轮到玩家回合();
                下棋(坐标, 棋子类型.玩家棋子);
            } else {
                坐标 = 轮到电脑回合();
                下棋(坐标, 棋子类型.电脑棋子);
            }

            裁判.检查(坐标);
            是否轮到玩家回合 = !是否轮到玩家回合;
        }

        private void 下棋(坐标类 坐标, char symbol) {
            棋盘[坐标.获取行坐标()][坐标.获取列坐标()] = symbol;
        }

        private 坐标类 轮到电脑回合() {
            return new 坐标类(1, 1);
        }

        private 坐标类 轮到玩家回合() {
            坐标类 坐标;
            while (!((坐标 = 玩家选择坐标()) != null &&
                    棋盘[坐标.获取行坐标()][坐标.获取列坐标()] == 棋子类型.空位));
            return 坐标;
        }

        private 坐标类 玩家选择坐标() {
            print("输入坐标: ");
            String text = 控制台扫描器.nextLine();
            try {
                String[] seg = text.split(" ");
                return new 坐标类(Integer.parseInt(seg[0].trim()), Integer.parseInt(seg[1].trim()));
            } catch (Exception e) {
                println("输入格式错误");
                return null;
            }
        }

        public boolean 产生胜利者() {
            return 裁判.是否产生赢家();
        }

        public void 游戏结束() {
            println("游戏结束");
        }

        // 对 System.out.print(Object) 的包装
        private static void print(Object o) {
            System.out.print(o);
        }

        // 对 System.out.println() 的包装
        public static void println() {
            System.out.println();
        }

        // 对 System.out.println(Object) 的包装
        public static void println(Object o) {
            System.out.println(o);
        }

    }

    static class 裁判类 {
        final char[][] 棋盘;
        final int 边长;
        final int 胜利条件;
        boolean 是否产生赢家;
        String 描述;

        // 棋盘, 获胜条件
        public 裁判类(char[][] 棋盘, int winningConditions) {
            this.棋盘 = 棋盘;
            this.边长 = 棋盘.length;
            this.胜利条件 = winningConditions;
        }

        public void 检查(坐标类 坐标) {
            char symbol = 棋盘[坐标.获取行坐标()][坐标.获取列坐标()];
            if (计算横线(坐标, symbol, false) ||
                计算竖线(坐标, symbol, false) ||
                计算左斜线(坐标, symbol, false) ||
                计算右斜线(坐标, symbol, false)
            )
                创建赢家(symbol);
        }

        private void 创建赢家(char symbol) {
            是否产生赢家 = true;
            if (symbol == 棋子类型.玩家棋子) {
                描述 = "玩家获胜";
            } else {
                描述 = "电脑获胜";
            }
        }

        private boolean 计算横线(坐标类 坐标, char 棋子符号, boolean 完成) {
            int 线上的玩家棋子个数 = 1;
            for (int 列 = 坐标.获取列坐标() + 1; 列< 边长; 列++) {
                if (棋盘[坐标.获取行坐标()][列] == 棋子符号) {
                    if (完成)
                        棋盘[坐标.获取行坐标()][列] = 棋子类型.完成标记;
                    线上的玩家棋子个数++;
                } else {
                    break;
                }
            }

            for (int 列 = 坐标.获取列坐标() - 1; 列>=0; 列--) {
                if (棋盘[坐标.获取行坐标()][列] == 棋子符号) {
                    if (完成)
                        棋盘[坐标.获取行坐标()][列] = 棋子类型.完成标记;
                    线上的玩家棋子个数++;
                } else {
                    break;
                }
            }

            if (线上的玩家棋子个数 >= 胜利条件) {
                if (!完成)
                    计算横线(坐标, 棋子符号, true);
                return true;
            } else {
                return false;
            }
        }

        private boolean 计算竖线(坐标类 坐标, char 棋子符号, boolean 完成) {
            int 线上的玩家棋子个数 = 1;
            for (int 行 = 坐标.获取行坐标() + 1; 行< 边长; 行++) {
                if (棋盘[行][坐标.获取行坐标()] == 棋子符号) {
                    if (完成)
                        棋盘[行][坐标.获取行坐标()] = 棋子类型.完成标记;
                    线上的玩家棋子个数++;
                } else {
                    break;
                }
            }

            for (int 行 = 坐标.获取行坐标() - 1; 行>=0; 行--) {
                if (棋盘[行][坐标.获取行坐标()] == 棋子符号) {
                    if (完成)
                        棋盘[行][坐标.获取行坐标()] = 棋子类型.完成标记;
                    线上的玩家棋子个数++;
                } else {
                    break;
                }
            }

            if (线上的玩家棋子个数 >= 胜利条件) {
                if (!完成)
                    计算竖线(坐标, 棋子符号, true);
                return true;
            } else {
                return false;
            }
        }

        private boolean 计算左斜线(坐标类 坐标, char 棋子符号, boolean 完成) {
            int 线上的玩家棋子个数 = 1;
            for (int 列 = 坐标.获取列坐标() + 1, 行 = 坐标.获取行坐标() + 1;
                 列 < 边长 && 行< 边长; 列++, 行++) {
                if (棋盘[行][列] == 棋子符号) {
                    if (完成)
                        棋盘[行][列] = 棋子类型.完成标记;
                    线上的玩家棋子个数++;
                } else {
                    break;
                }
            }

            for (int 列 = 坐标.获取列坐标() - 1, 行 = 坐标.获取行坐标() - 1;
                 列 >= 0 && 行>=0; 列--, 行--) {
                if (棋盘[行][列] == 棋子符号) {
                    if (完成)
                        棋盘[行][列] = 棋子类型.完成标记;
                    线上的玩家棋子个数++;
                } else {
                    break;
                }
            }

            if (线上的玩家棋子个数 >= 胜利条件) {
                if (!完成)
                    计算左斜线(坐标, 棋子符号, true);
                return true;
            } else {
                return false;
            }
        }

        private boolean 计算右斜线(坐标类 坐标, char 棋子符号, boolean 完成) {
            int 线上的玩家棋子个数 = 1;
            for (int 列 = 坐标.获取列坐标() + 1, 行 = 坐标.获取行坐标() - 1;
                 列 < 边长 && 行>=0; 列++, 行--) {
                if (棋盘[行][列] == 棋子符号) {
                    if (完成)
                        棋盘[行][列] = 棋子类型.完成标记;
                    线上的玩家棋子个数++;
                } else {
                    break;
                }
            }

            for (int 列 = 坐标.获取列坐标() - 1, 行 = 坐标.获取行坐标() + 1; 列 >= 0 && 行< 边长; 列--, 行++) {
                if (棋盘[列][行] == 棋子符号) {
                    if (完成)
                        棋盘[列][行] = 棋子类型.完成标记;
                    线上的玩家棋子个数++;
                } else {
                    break;
                }
            }

            if (线上的玩家棋子个数 >= 胜利条件) {
                if (!完成)
                    计算右斜线(坐标, 棋子符号, true);
                return true;
            } else {
                return false;
            }
        }

        public boolean 是否产生赢家() {
            return 是否产生赢家;
        }
    }

    static class 坐标类 {
        private final int 列;
        private final int 行;
        public 坐标类(int row, int column) {
            this.行 = row - 1;
            this.列 = column - 1;
        }
        public int 获取列坐标() {
            return 列;
        }
        public int 获取行坐标() {
            return 行;
        }
    }

    interface 棋子类型 {
        char 空位 = '+';
        char 玩家棋子 = 'X';
        char 电脑棋子 = '0';
        char 完成标记 = '#';
    }

//    @Test
//    public void test0() {
//        char[][] board = {
//                "++++0+++".toCharArray(),
//                "++++0+++".toCharArray(),
//                "+0++0+++".toCharArray(),
//                "++0+0+++".toCharArray(),
//                "+++00+++".toCharArray(),
//                "++++0+++".toCharArray(),
//                "00+000++".toCharArray(),
//                "++++++++".toCharArray(),
//        };
//        Referee referee = new Referee(board, 5);
////        referee.inspection(new Point(7, 1));
//        referee.inspection(new Point(6, 5));
//        System.out.println(referee.hasWinner);
//    }

}
